//============================================================================
// Name        : queryMain.cpp
// Author      : lucmir
// Description :
//============================================================================

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <sys/time.h>
#include <cstdlib>
#include <iostream>

#include "Lexon.h"
#include "Parser.h"
#include "IndexBuilder.h"
#include "TimeMeasurement.h"

using namespace std;

// Functions prototypes
unsigned int getIntersection(QueryResult *qrA, QueryResult *qrB, std::list<unsigned int> *result);
unsigned int getUnion(QueryResult *qrA, QueryResult *qrB, std::list<unsigned int> *result);
QueryResult *makeQuery(IndexBuilder *indexBuilder, std::string term);
void makeOperation(std::string term1, std::string term2, std::string op, QueryResult *qr1, QueryResult *qr2);
bool getArgs (int argc, char **argv, char **indexFileName, char **vocabularyFileName, char **documentsFileName);


int main(int argc, char **argv) {

	// Load parameters
	char *indexFileName = NULL, *vocabularyFileName = NULL, *documentsFileName = NULL;
	if (! getArgs(argc, argv, &indexFileName, &vocabularyFileName, &documentsFileName) ) {
		return EXIT_SUCCESS;
	}

	// Load vocabulary
	IndexBuilder *indexBuilder = new IndexBuilder();
	std::cout << "\nCarregando vocabulário...";
	std::flush(std::cout);
	bool result = indexBuilder->getLexon()->readFromFile( std::string(vocabularyFileName) );
		if(!result) {
			return EXIT_FAILURE;
		}

	// Load document
	std::cout << "\nCarregando informações dos documentos...";
	std::flush(std::cout);
	result = indexBuilder->getDocIdx()->readFromFile( std::string(documentsFileName) );
	if(!result) {
		return EXIT_FAILURE;
	}

	// Query index
	result = indexBuilder->getIndex()->openToRead( std::string(indexFileName) );
	if(!result) {
		std::cout << "\nERRO: Não foi possível abrir o arquivo: \"" << indexFileName << "\"\n\n";
		return EXIT_SUCCESS;
	}
	std::cout << "\t[  OK  ]\n";

	// Make queries
	QueryResult *qr1, *qr2;
	while(true) {
		// Read query from user
		std::cout << "\nDigite\t\"exit/EXIT\" para sair ou \n\t\"termo1[ AND/and/OR/or termo2]\" para consulta: ";
		std::flush(std::cout);
		std::string query="", term1="", op="", term2="", term="";
		getline(cin, query);
		std::istringstream istr(query);
		istr >> term;
		if(term.size() > 0) {
			term1 = Parser::preprocessQueryTerm(term);
		}
		istr >> op;
		term.clear();
		istr >> term;
		if(term.size() > 0) {
			term2 = Parser::preprocessQueryTerm(term);
		}

		// Start time measurement
		TimeMeasurement tm;
		tm.setUsrTime();
		tm.setSysTime();
		tm.setTotalTime();

		// Finish execution?
		if(term1 == "exit" || term1 == "EXIT") {
			// Query index
			indexBuilder->getIndex()->closeToRead();
			return EXIT_SUCCESS;
		}

		// Query for term1
		std::cout << "\nResultado:\n\n";
		qr1 = makeQuery(indexBuilder, term1);
		if(term2 != "") {

			// Query for term2
			std::cout << "\n";
			qr2 = makeQuery(indexBuilder, term2);

			// Operation "AND" or "OR"
			makeOperation(term1, term2, op, qr1, qr2);

			delete qr2;
		}
		delete qr1;

		// Finish time measurement
		tm.setUsrTime();
		tm.setSysTime();
		tm.setTotalTime();
		std::cout << "\n\t";
		tm.print();
		tm.clear();
		std::cout << "--------------------------------------------------\n";
	}

	return EXIT_SUCCESS;
}

void usage() {
	std::cout << "\n\tPrograma para execução de consultas em arquivo-invertido.\n\n"
		<< "\tParâmetros:\n"
		<< "\t\t-a <arquivo-invertido>\n"
		<< "\t\t-v <vocabulário>\n"
		<< "\t\t-d <documentos>\n\n";
}

bool getArgs (int argc, char** argv, char **indexFileName, char **vocabularyFileName, char **documentsFileName) {
	int opt;
	while((opt = getopt(argc,argv,"a:v:d:h"))!=-1) {
		switch(opt) {
			case 'h':
				usage();
				return false;
			case 'a':
				*indexFileName = optarg;
				break;
			case 'v':
				*vocabularyFileName = optarg;
				break;
			case 'd':
				*documentsFileName = optarg;
				break;
			default:
				usage();
				return false;
		}
	}
	if(*indexFileName == NULL || *vocabularyFileName == NULL || *documentsFileName == NULL) {
		usage();
		return false;
	}
	return true;
}

QueryResult *makeQuery(IndexBuilder *indexBuilder, std::string term) {
	QueryResult *qr = indexBuilder->getIndex()->queryTerm(term);

	if (qr == NULL) {
		std::cout << "\tTermo: \"" << term << "\"\n" << "\tNenhuma ocorrência.\n";
	} else {
		qr->printDocFreqs();
	}

	return qr;
}

void makeOperation(std::string term1, std::string term2, std::string op, QueryResult *qr1, QueryResult *qr2) {
	std::list<unsigned int>::iterator itr;
	std::list<unsigned int> result;
	unsigned int count = 0;

	if(op != "") {
		// Intersection
		if(op == "AND" || op == "and") {
			count = getIntersection(qr1, qr2, &result);
			std::cout << "\n\t\"" << term1 << "\" AND \"" << term2 << "\"\n";
			std::cout << "\tTotal: " << count << "\n";
		}
		// Union
		else if (op == "OR" || op == "or") {
			std::list<unsigned int> unionSet;
			count = getUnion(qr1, qr2, &result);
			std::cout << "\n\t\"" << term1 << "\" OR \"" << term2 << "\"\n";
			std::cout << "\tTotal: " << count << "\n";
		}
		else {
			std::cout << "\n\tNão foi possível identificar o operador: \"" << op << "\"\n";
			return;
		}
	}
	if(count > 0) {
		std::cout << "\tDocumentos:";
		for(itr=result.begin(); itr!=result.end(); itr++) {
			std::cout << " " << *itr;
		}
		std::cout << "\n";
	}
}

unsigned int getIntersection(QueryResult *qrA, QueryResult *qrB, std::list<unsigned int> *result) {
	unsigned int *docFreqsA, *docFreqsB;
	unsigned int tf=0, a=0, b=0;

	if(qrA == NULL || qrB == NULL) {
		return 0;
	}

	docFreqsA = qrA->getDocFreqs();
	docFreqsB = qrB->getDocFreqs();
	while( (a<2*qrA->getTf()) && (b<2*qrB->getTf()) ) {
		if (docFreqsA[a] == docFreqsB[b]) {
			result->push_back(docFreqsA[a]);
			tf++;
			a+=2;
			b+=2;
		}
		else if(docFreqsA[a] < docFreqsB[b]) {
			a+=2;
		}
		else {
			b+=2;
		}
	}

	return tf;
}

unsigned int getUnion(QueryResult *qrA, QueryResult *qrB, std::list<unsigned int> *result) {
	unsigned int *docFreqsA, *docFreqsB;
	unsigned int tf=0, a=0, b=0;

	if(qrA == NULL && qrB == NULL) {
		return 0;
	}
	else if(qrB == NULL) {
		docFreqsA = qrA->getDocFreqs();
		while(a<qrA->getTf()*2) {
			result->push_back(docFreqsA[a]);
			a+=2;
			tf++;
		}
		return tf;
	}
	else if(qrA == NULL) {
		docFreqsB = qrB->getDocFreqs();
		while (b < qrB->getTf() * 2) {
			result->push_back(docFreqsB[b]);
			b += 2;
			tf++;
		}
		return tf;
	}

	docFreqsA = qrA->getDocFreqs();
	docFreqsB = qrB->getDocFreqs();
	while( (a<2*qrA->getTf()) && (b<2*qrB->getTf()) ) {
		if(docFreqsA[a] < docFreqsB[b]) {
			// DocId
			result->push_back(docFreqsA[a]);
			a+=2;
		}
		else if(docFreqsB[b] < docFreqsA[a]) {
			// DocId
			result->push_back(docFreqsB[b]);
			b+=2;
		}
		else {
			// DocId
			result->push_back(docFreqsA[a]);
			a+=2;
			b+=2;
		}
		tf++;
	}
	while(a<2*qrA->getTf()) {
		result->push_back(docFreqsA[a]);
		a+=2;
		tf++;
	}
	while(b<2*qrB->getTf()) {
		result->push_back(docFreqsB[b]);
		b+=2;
		tf++;
	}

	return tf;
}

